#include "dag.h"
#include "dag_types.h"

#include <map>
#include <limits>
#include <iostream>
#include <stdarg.h>

using namespace dag;
using namespace std;

const size_t Link::unconnected = std::numeric_limits<size_t>::max();

Link::Link() {
}

Link Link::fromRanges(size_t numRanges, ...) {
	Link newLink; 
	
	va_list rangeList;
	va_start(rangeList, numRanges);
	
	for (size_t i = 0; i < numRanges; i++) {
		size_t inputStart = va_arg(rangeList, size_t);
		size_t inputEnd = va_arg(rangeList, size_t);
		size_t outputStart = va_arg(rangeList, size_t);
		size_t outputEnd = va_arg(rangeList, size_t);
		
		
		
		newLink.linkRange(inputStart, inputEnd,
							 outputStart, outputEnd);
	}
	
	va_end(rangeList);
	
	return newLink;
}

Link Link::fromConnections(size_t numConnections, ...) {
	Link newLink; 
	
	va_list rangeList;
	va_start(rangeList, numConnections);
	
	for (size_t i = 0; i < numConnections; i++) {
		size_t input = va_arg(rangeList, size_t);
		size_t output = va_arg(rangeList, size_t);
		
		newLink.link(input, output);
	}
	
	va_end(rangeList);
	
	return newLink;
}

Link Link::fullyConnected(size_t numConnections) {
	Link newLink;
	
	for (size_t i = 0; i < numConnections; i++) {
		newLink.link(i, i);
	}
	return newLink;
}

Link Link::fromArrays(size_t len, 
					  const size_t* forwardArray,
					  const size_t* reverseArray) {
	Link newLink;
	for (size_t i = 0; i < len; i++) {
		newLink.link(forwardArray[i], reverseArray[i]);
	}
	return newLink;
}

Link Link::inflate(istream& binaryStream) {
	Link newLink;
	dag_uint32_t input, output, numConnections;
	binaryStream.read((char*)&numConnections, 
					  dag_uint32_size);
	
	for (dag_uint32_t i = 0; i < numConnections; i++) {
		binaryStream.read((char*)&input, dag_uint32_size);
		binaryStream.read((char*)&output, dag_uint32_size);
		newLink.link(input, output);
	}
	
	return newLink;
}

Link& Link::link(size_t inputPos, size_t outputPos) {
	/* check if only one of them is already linked.  Need to make sure we don't
	 * end up in a state where there are ambiguous links when the mapping
	 * is reversed */
	if ((m_ForIdxs.count(inputPos) + m_RevIdxs.count(outputPos)) == 1) {
		if (m_ForIdxs.count(inputPos) == 1) {
			unlink(inputPos, m_ForMap[inputPos]);
		}
		else {
			unlink(m_RevMap[outputPos], outputPos);
		}
	}
	
	m_ForIdxs.insert(inputPos);
	m_RevIdxs.insert(outputPos);
	m_ForMap[inputPos] = outputPos;
	m_RevMap[outputPos] = inputPos;
	
	return *this;
}

Link& Link::unlink(size_t inputPos, size_t outputPos) {
	if (m_ForMap.count(inputPos) > 0) {
		if (m_ForMap[inputPos] == outputPos) {
			m_ForMap.erase(inputPos);
			m_ForIdxs.erase(inputPos);
		}
	}
	
	if (m_RevMap.count(outputPos) > 0) {
		if (m_RevMap[outputPos] == inputPos) {
			m_RevMap.erase(outputPos);
			m_RevIdxs.erase(outputPos);
		}
	}
	
	return *this;
}

Link& Link::linkRange(size_t inputStart, size_t inputEnd,
					  size_t outputStart, size_t outputEnd) {
	if (inputStart > inputEnd) {
		throw InvalidArgument();
	}
	
	if (outputStart > outputEnd) {
		throw InvalidArgument();
	}
	
	if ((outputEnd - outputStart) != (inputEnd - inputStart)) {
		throw InvalidArgument();
	}
	
	while((inputStart <= inputEnd) && (outputStart <= outputEnd)) {
		link(inputStart, outputStart);
		inputStart++;
		outputStart++;
	}
	
	return *this;
}
Link& Link::unlinkRange(size_t inputStart, size_t inputEnd,
						size_t outputStart, size_t outputEnd) {
	if (inputStart > inputEnd) {
		throw InvalidArgument();
	}
	
	if (outputStart > outputEnd) {
		throw InvalidArgument();
	}
	
	if ((outputEnd - outputStart) != (inputEnd - inputStart)) {
		throw InvalidArgument();
	}
	
	while ((inputStart <= inputEnd) && (outputStart <= outputEnd)) {
		unlink(inputStart, outputStart);
		inputStart++;
		outputStart++;
	}
	
	return *this;
}

size_t Link::forward(size_t inputPos) const {
	if (m_ForMap.count(inputPos) == 0) {
		return unconnected;
	}
	
	return m_ForMap.find(inputPos)->second;
}

size_t Link::reverse(size_t outputPos) const {
	if (m_RevMap.count(outputPos) == 0) {
		return unconnected;
	}
	
	return m_RevMap.find(outputPos)->second;
}

void Link::clear() {
	m_ForMap.clear();
	m_RevMap.clear();
	m_ForIdxs.clear();
	m_RevIdxs.clear();
}

const set<size_t>& Link::forwardSet() const {
	return m_ForIdxs;
}

const set<size_t>& Link::reverseSet() const {
	return m_RevIdxs;
}

size_t Link::size() const {
	return m_ForIdxs.size();
}

void Link::asArrays(size_t* forwardArray, size_t* reverseArray) const {
	if (m_ForIdxs.size() != m_RevIdxs.size() ||
		m_RevIdxs.size() != m_ForMap.size() ||
		m_ForMap.size() != m_RevMap.size()) {
		throw DagError("corrupt link");
	}
	
	map<size_t, size_t>::const_iterator iter;
	size_t pos = 0;
	for (iter = m_ForMap.begin(); iter != m_ForMap.end(); iter++) {
		forwardArray[pos] = iter->first;
		reverseArray[pos] = iter->second;
		pos++;
	}
}


void Link::serialize(std::ostream& binaryStream) const {
	dag_uint32_t numConnections = m_ForMap.size();
	if (m_RevMap.size() != numConnections) {
		throw DagError("corrupt link");
	}
	
	binaryStream.write((const char*)&numConnections, dag_uint32_size);
	
	map<size_t, size_t>::const_iterator conIter = m_ForMap.begin();
	while (conIter != m_ForMap.end()) {
		dag_uint32_t idx;
		idx = conIter->first;
		binaryStream.write((const char*)&idx, dag_uint32_size);
		idx = conIter->second;
		binaryStream.write((const char*)&idx, dag_uint32_size);
		conIter++;
	}
}
